# /**
# * Copyright (c) 2022 Beijing Jiaotong University
# * PhotLab is licensed under [Open Source License].
# * You can use this software according to the terms and conditions of the [Open Source License].
# *
# * You may obtain a copy of [Open Source License] at: [https://open.source.license/]
# *
# * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# *
# * See the [Open Source License] for more details.
# */
# /**
# * Author: Shihao Xie
# * Created: Jul. 13, 2022
# * Supported by: National Key Research and Development Program of China
# */
import os.path
import time

from phot_server.core.exception import FilePathError, TemporaryResourceException, ResourceIsUpdatedException
from phot_server.factory.graph_factory import GraphFactory
from phot_server.rpc.graph_data_type.ttypes import PhotGraph, RenderRect
from phot_server.repository.component_prototype_repository import ComponentPrototypeRepository
from phot_server.repository.phot_graph_repository import GraphRepository
from phot_server.repository.project_config_repository import ProjectConfigRepository
from phot_server.repository.render_graph_repository import RenderGraphRepository
from phot_server.utils.phot_file import GRAPH_POSTFIX
from phot_server.utils.phtg import parse_phtg, save_phtg
from phot_server.utils.type_convert import PhotComponentPrototype_to_PhotComponent, PhotComponentPrototype_to_RenderGraphNode
from phot_server.utils.generate_uuid import generate_uuid


class GraphManager:

    def __init__(self):
        self.__graph_repository = GraphRepository()
        self.__render_repository = RenderGraphRepository()
        self.__project_repository = ProjectConfigRepository()
        self.__component_library_repository = ComponentPrototypeRepository()

    # Logical model management interface
    #  file
    def open_graph(self, file_path):
        if file_path is None:
            raise FilePathError("Graph path is None.")
        file_path = os.path.abspath(file_path)
        graph, render_graph = parse_phtg(file_path)
        graph.graphPath = file_path
        self.__graph_repository.add_graph(graph)
        self.__render_repository.add_render_graph(render_graph)
        return self.get_graph(graph.graphId)

    def save_graph(self, graph_id):
        graph = self.__graph_repository.get_graph(graph_id)
        config = self.__project_repository.get_project_config_by_name(graph.name + GRAPH_POSTFIX)
        if graph.graphPath is not None:
            path = graph.graphPath
        elif config is not None:
            path = os.path.abspath(os.path.join(config.projectDirectory, config.mainFileName + GRAPH_POSTFIX))
        else:
            raise TemporaryResourceException(graph_id)
        render_graph = self.__render_repository.get_render_graph(graph_id)
        save_phtg(path, graph, render_graph)
        self.__graph_repository.update[graph_id] = False

    def close_all_opened_graph(self, save=True):
        from copy import deepcopy
        graph_ids = list(self.__graph_repository.get_graphs().keys())
        for graph_id in graph_ids:
            if save:
                graph = self.__graph_repository.get_graph(graph_id)
                config = self.__project_repository.get_project_config_by_name(graph.name + GRAPH_POSTFIX)
                if graph.graphPath is not None:
                    path = graph.graphPath
                elif config is not None:
                    path = os.path.abspath(os.path.join(config.projectDirectory, config.mainFileName + GRAPH_POSTFIX))
                else:
                    raise TemporaryResourceException(graph_id)
                render_graph = self.__render_repository.get_render_graph(graph_id)
                save_phtg(path, graph, render_graph)

            self.__graph_repository.delete_graph(graph_id)
            self.__render_repository.delete_render_graph(graph_id)

    def graph_save_as(self, graph_id, file_path):
        graph = self.__graph_repository.get_graph(graph_id)
        render_graph = self.__render_repository.get_render_graph(graph_id)
        if os.path.exists(os.path.realpath(file_path)):
            raise FilePathError(f"The file path {os.path.realpath(file_path)} already exists. Please specify a new file path")
        else:
            save_phtg(file_path, graph, render_graph)
        if graph.graphPath is None:
            graph.graphPath = file_path

    def new_empty_graph(self, graph_name=None):
        factory = GraphFactory()
        graph = factory.get_new_phot_graph(graph_name)
        render_graph = factory.get_new_render_graph(graph.graphId)
        self.__graph_repository.add_graph(graph)
        self.__render_repository.add_render_graph(render_graph)
        return graph

    def add_graph(self, graph):
        if graph.graphId is None or graph.graphId == "":
            return None
        return self.__graph_repository.add_graph(graph)

    def get_graph(self, graph_id) -> PhotGraph:
        return self.__graph_repository.get_graph(graph_id)

    def has_graph(self, graph_id) -> bool:
        return self.__graph_repository.has_graph(graph_id)

    def close_graph(self, graph_id, ignore_change=False):
        graph = self.__graph_repository.get_graph(graph_id)
        if ignore_change is False and graph.graphPath is None:
            raise TemporaryResourceException(graph_id)
        is_updated = self.__graph_repository.update.get(graph_id, None)
        if is_updated is not None and is_updated and ignore_change is False:
            raise ResourceIsUpdatedException(graph_id)
        self.__graph_repository.delete_graph(graph_id)
        self.__render_repository.delete_render_graph(graph_id)

    def rename_graph(self, graph_id, new_name):
        graph_name = self.__graph_repository.get_graph(graph_id).name + GRAPH_POSTFIX
        print(graph_name)
        config = self.__project_repository.get_project_config_by_name(graph_name)
        if config is not None:
            config.mainFileName = new_name
        self.__graph_repository.rename_graph(graph_id, new_name)
        self.__render_repository.rename_render_graph(graph_id, new_name)
        graph = self.__graph_repository.get_graph(graph_id)
        if graph.graphPath is not None:
            directory = os.path.dirname(graph.graphPath)
            new_path = os.path.join(directory, new_name + GRAPH_POSTFIX)
            os.rename(graph.graphPath, new_path)
            graph.graphPath = new_path
        return graph

    def get_opened_graph_list(self):
        graphs = self.__graph_repository.get_graphs()
        id_name_dict = dict()
        for graph in graphs.values():
            id_name_dict.update({graph.graphId: graph.name})
        return id_name_dict

    #  master information
    def alter_graph_attribute(self, graph_id, key, value):
        self.__graph_repository.update_graph_attributes(graph_id, {key: value})

    #  server_config parameter
    def get_graph_parameter(self, graph_id, parameter_name):
        return self.__graph_repository.get_graph_parameter(graph_id, parameter_name)

    def get_graph_parameters(self, graph_id):
        return self.__graph_repository.get_graph_parameters(graph_id)

    def alter_graph_parameter(self, graph_id, parameter_name, parameter):
        self.__graph_repository.alter_graph_parameter(graph_id, parameter_name, parameter)

    def alter_graph_parameters(self, graph_id, parameter_list):
        self.__graph_repository.alter_graph_parameters(graph_id, parameter_list)

    def add_graph_parameter(self, graph_id, parameter):
        return self.__graph_repository.add_graph_parameter(graph_id, parameter)

    def delete_graph_parameter(self, graph_id, parameter_name):
        self.__graph_repository.delete_graph_parameter(graph_id, parameter_name)

    # component
    def get_component(self, graph_id, component_id):
        return self.__graph_repository.get_component(graph_id, component_id)

    def get_components(self, graph_id):
        return self.__graph_repository.get_components(graph_id)

    def add_component(self, graph_id, component):
        if component.componentId is None:
            component.componentId = generate_uuid()
        return self.__graph_repository.add_component(graph_id, component)

    def add_component_from_library(self, graph_id, component_path, rect):
        component_prototype = self.__component_library_repository.get_component_prototype_from_file(component_path)
        uid = generate_uuid()
        component = self.__graph_repository.add_component(graph_id,
                                                          PhotComponentPrototype_to_PhotComponent(component_prototype,
                                                                                                  uid))
        render_component = self.__render_repository.add_node(graph_id,
                                                             PhotComponentPrototype_to_RenderGraphNode(
                                                                 component_prototype, uid,
                                                                 rect))
        return component

    def alter_component(self, graph_id, component):
        self.__graph_repository.alter_component(graph_id, component)

    def delete_component(self, graph_id, component_id, cascade_delete=False):
        self.__graph_repository.delete_component(graph_id, component_id)
        if cascade_delete:
            self.delete_render_node(graph_id, component_id, False)
            for link in self.__graph_repository.get_links(graph_id):
                if link.srcNodeId == component_id or link.dstNodeId == component_id:
                    self.delete_link(graph_id, link.linkId, True)

    #  link
    def add_link(self, graph_id, link):
        if link.linkId is None:
            link.linkId = generate_uuid()
        return self.__graph_repository.add_link(graph_id, link)

    def get_link(self, graph_id, link_id):
        return self.__graph_repository.get_link(graph_id, link_id)

    def get_links(self, graph_id):
        return self.__graph_repository.get_links(graph_id)

    def alter_link(self, graph_id, link):
        self.__graph_repository.alter_link(graph_id, link)

    def alter_links(self, graph_id, links):
        self.__graph_repository.alter_links(graph_id, links)

    def delete_link(self, graph_id, link_id, delete_render=False):
        self.__graph_repository.delete_link(graph_id, link_id)
        if delete_render:
            self.__render_repository.delete_route(graph_id, link_id)

    # Render model management interfaces
    # render_graph
    def get_render_graph(self, graph_id):
        return self.__render_repository.get_render_graph(graph_id)

    def delete_render_graph(self, graph_id):
        self.__render_repository.delete_render_graph(graph_id)

    def add_render_node(self, graph_id, node):
        return self.__render_repository.add_node(graph_id, node)

    def get_render_node(self, graph_id, node_id):
        return self.__render_repository.get_node(graph_id, node_id)

    def get_render_nodes(self, graph_id):
        return self.__render_repository.get_nodes(graph_id)

    def delete_render_node(self, graph_id, node_id, cascade_delete=False):
        self.__render_repository.delete_node(graph_id, node_id)
        if cascade_delete:
            self.delete_component(graph_id, node_id, False)
            for link in self.__graph_repository.get_links(graph_id):
                if link.srcNodeId == node_id or link.dstNodeId == node_id:
                    self.delete_link(graph_id, link.linkId, True)

    def set_render_node_rect(self, graph_id, node_id, rect):
        self.__render_repository.set_node_rect(graph_id, node_id, rect)

    def render_node_move_to(self, graph_id, node_id, x, y):
        self.__render_repository.set_node_point(graph_id, node_id, x, y)

    def alter_render_node(self, graph_id, node):
        self.__render_repository.alter_node(graph_id, node)

    #  text_area
    def get_render_route(self, graph_id, route_id):
        return self.__render_repository.get_route(graph_id, route_id)

    def get_render_route_list(self, graph_id):
        return self.__render_repository.get_routes(graph_id)

    def add_render_route(self, graph_id, route):
        return self.__render_repository.add_route(graph_id, route)

    def alter_render_route(self, graph_id, route):
        self.__render_repository.alter_route(graph_id, route)

    def alter_render_route_list(self, graph_id, route_list):
        self.__render_repository.alter_route_list(graph_id, route_list)

    def delete_render_route(self, graph_id, route_id, delete_model=False):
        self.__render_repository.delete_route(graph_id, route_id)
        if delete_model:
            self.delete_link(graph_id, route_id, False)

    def set_render_route_path(self, graph_id, route_id, path):
        self.__render_repository.set_route_path(graph_id, route_id, path)

    def get_text_area(self, graph_id, text_id):
        return self.__render_repository.get_text_area(graph_id, text_id)

    def get_text_area_list(self, graph_id):
        return self.__render_repository.get_text_area_list(graph_id)

    def add_text_area(self, graph_id, text_area):
        if text_area.textId is None:
            text_area.textId = generate_uuid()
        return self.__render_repository.add_text_area(graph_id, text_area)

    def alter_text_area(self, graph_id, text_area):
        self.__render_repository.alter_text_area(graph_id, text_area)

    def alter_text_area_list(self, graph_id, text_area_list: list):
        self.__render_repository.alter_text_area_list(graph_id, text_area_list)

    def delete_text_area(self, graph_id, text_id):
        self.__render_repository.delete_text_area(graph_id, text_id)

    def set_text_area_rect(self, graph_id, text_id, rect):
        self.__render_repository.set_text_area_rect(graph_id, text_id, rect)

    def set_text(self, graph_id, text_id, text):
        self.__render_repository.set_text(graph_id, text_id, text)
